Random Data Generators Java
Random Data Generators for API Testing in Java
One of the major problems of a test automation framework is the change of data that makes the tests flaky. Because if someone/or some process changes the data that was used by the test, the test would fail. One way to solve this issue is to create test data as part of the test itself. This makes test more robust because the data is now part of the test and it can’t change. In a previous post we discussed about the strategies to generate test data using POJO
s and Lombok
’s builder. This is a continuation of that post, but it’s more focused on the use of random data instead of using static values. With random data, we are communicating that the particular piece of data is not relevant to the specific test. We want to generate data to reduce the amount of boilerplate, and make tests clear in terms of what data is required to exercise the test without generating a bunch of noise that is not relevant to the test. In a sense, we aspire to create robust tests by using random values.
There are various ways to generate random data in Java, but here we will discuss mainly about Java Faker, JsonTemplate and random-beans libraries. We would see how we can use these libraries in conjunction with REST-assured
to run an automated functional API test.
Java Faker
Java Faker can be used to generate a variety of real looking data. In order to use Java Faker
in a project, we need to add the following maven
dependency to our POM
:
<dependency>
<groupId>com.github.javafaker</groupId>
<artifactId>javafaker</artifactId>
<version>0.17.2</version>
</dependency>
Java Faker
provides us with FakerValueService
that in turn contains methods to generate random sequences of letters, numbers and both.
letterify(String letterString)
method allows us to generate random alphabetic characters for the ?
characters in theletterString
that is passed to letterify()
.
numerify(String numberString)
method allows us to generate random numerical characters for the #
characters in the numberString
that is passed to numerify()
.
bothify(String alphanumericString)
method allows us to generate random alphanumeric characters for the ?
and #
characters in the alphanumericString
that is passed to bothify()
.
regexify(String regex)
method allows us to generate a String that matches the given regular expression.
FakeValuesService faker = new FakeValuesService(
new Locale("en-US"), new RandomService());
faker.letterify("12??89"); //will return something like "12hZ89"
faker.numerify("ABC##EF"); //will return something like "ABC99EF"
faker.bothify("12??##ED"); //will return something like "12iL27ED"
faker.regexify("[a-z1-9]{4}"); //will return something like "6bJ1"
Now, the question arises that how we can use the random values generated by the FakeValueService
to create random data in our test. For this specific purpose, we can leverage POJO
s that use Lombok
’s @Builder
annotation. Furthermore, we can use @Default
annotation for the Builder
to generate random data by default. So when we call POJO.builder().build()
it should generate a POJO
object with all fields auto populated by random values. One advantage of this approach is that we can override the random values in the builder to have a hybrid of random and hard coded values.
@Getter
@Builder
public class Document {
@Default
private String title = faker.regexify("[a-z1-9]{10}");
@Default
private String socialTitle = faker.regexify("[a-z1-9]{10}");
@Default
private String heading = faker.regexify("[a-z1-9]{10}");
@Default
private String description = faker.regexify("[a-z1-9]{10}");
}
@Test
public void testDefaultValues() {
// this will generate a document with random values auto
// populated for all fields as per the regex
Document doc = Document.builder().build();
Document response = given().
spec(RequestSpec).
body(doc).
post("/document").
as(Document.class);
assertThat(response, sameBeanAs(doc));
}
@Test
public void testHybridValues() {
// this will generate a document with random values
// overridden for the methods called in builder
Document doc = Document.builder().
.description("description")
.title("title")
.build();
Document response = given().
spec(RequestSpec).
body(doc).
post("/document").
as(Document.class);
assertThat(response, sameBeanAs(doc));
}
JsonTemplate
JsonTemplate is a tool to generate json
Strings. JsonTemplate
allows us to generate a schema-compatible json
without bothering about the specific values. In order to use JsonTemplate
in a project, we will need the following dependency in our POM
:
<dependency>
<groupId>com.github.json-template</groupId>
<artifactId>jsontemplate</artifactId>
<version>0.2.1</version>
</dependency>
JsonTemplate
, @x
can refer to a value producer such as @s
which generates a random String for you. There are other value producers such as @i
for integers, @f
for floats, @ip
for an ip String. If a map parameter
is given, the value producer produces a value according to the map values. For example, @s(min=10, max=20)
will produce a String with a length between 10 and 20.
For the Document class given above, we can write a JsonTemplate
and use in a test as following:
@Test
public void testJsonTemplate() {
String template = new JsonTemplate(new StringBuilder()
.append("{")
.append("title:@s(min=10, max=20),")
.append("socialTitle:@s(min=10, max=20),")
.append("heading:@s(min=10, max=20),")
.append("description:@s(min=10, max=20)")
.append("}")
.toString()).prettyString();
Document doc = new Gson().fromJson(template,
Document.class);
Document response = given().
spec(RequestSpec).
body(doc).
post("/document").
as(Document.class);
assertThat(response, sameBeanAs(doc));
}
As we can see from above example that it’s not very easy to produce templates because it requires us to build a String first. If the json
happens to be nested, then it can easily become a nightmare to maintain these templates.
Random Beans
With Random Beans we can generate beans with random values for all fields. It provides us with a nextObject(Class class)
method, which produces a random Object of the class that was passed. One of the main advantages of Random Beans
library is that it can create an Object graph of all the Objects used in a class with nested structure. In order to use Random Beans in a project, we will need the following dependency:
<dependency>
<groupId>io.github.benas</groupId>
<artifactId>random-beans</artifactId>
<version>3.8.0</version>
</dependency>
Random Beans can be used to create random data for a test. Using EnhancedRandom
class we can configure our random values for certain types as follows :
EnhancedRandom enhancedRandom = EnhancedRandomBuilder.aNewEnhancedRandomBuilder()
.seed(123L)
.objectPoolSize(100)
.stringLengthRange(4, 10)
.collectionSizeRange(1, 10)
.scanClasspathForConcreteTypes(true)
.build();
Once set, these parameters will be applied to all fields of the Object graph. stringLengthRange
tells EnhancedRandom
to generate a Strings with bounded size. When we set a seed
value, each run produces the same random value. This feature is useful when one wants stable random values across the JVM restarts. We can learn more about these parameters here as well.
By default, Random Beans generates random values according to field type. However, we can use Randomizer
interface to generate more meaningful random values. For instance, if we want to choose a random value from a set of values we can implement the Randomizer
interface and override it’s getRandomValue()
method to produce our custom random value as follows:
public class NumericalStringRandomizer implements Randomizer<String> {
@Override
public String getRandomValue() {
return String.valueOf(new Random().nextInt(99000) + 1000);
}
}
Using the above NumericalStringRandomizer
we can get a random String with values ranging from “1000” to “100000” . In order to be able to use NumericalStringRandomizer
we will need to annotate the field with @Randomizer
in our class.
@Randomizer(NumericalStringRandomizer.class)
private String key;
If we want to exclude a particular field from producing a random value, we can annotate that field with @Exclude
as follows:
@Exclude
private Long id;
Following is an example of a class that can generate a random bean with different type of randomizers that one can define:
@Getter
public class Document {
@Exclude
private Long id; private String title;
private String socialTitle;
private String heading;
@Randomizer(NumericalStringRandomizer.class)
private String key;
private String description;
}
We can use the class above in our REST-assured
test as follows :
@Test
public void testDocumentForMetaData() {
EnhancedRandom enhancedRandom = EnhancedRandomBuilder
.aNewEnhancedRandomBuilder()
.objectPoolSize(100)
.stringLengthRange(4, 10)
.build();
Document doc = enhancedRandom.nextObject(Document.class);
Document response = given().
spec(RequestSpec).
body(doc).
post("/document").
as(Document.class);
assertThat(response, sameBeanAs(doc));
});
}
One problem with using Random Beans to generate test data is that it requires us to create Randomizers
for fields that require static values. There is not an easy work around for generating a random bean with both random and static values.
This was a brief analysis of some of a few random data generator java libraries available to use and how we could use them to generate test data. Hope you enjoyed reading the blog post and were able to learn about generating random test data from it. Thanks a lot for reading!